package com.marketlive.system.trace.hibernate;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.hibernate.EmptyInterceptor;
import org.hibernate.CallbackException;
import org.hibernate.type.Type;
import org.springframework.context.ApplicationContext;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Iterator;

import com.marketlive.entity.account.Contact;
import com.marketlive.entity.IntegerPk;

/**
 * Interceptor is used to log information about persistence activity for display
 * in the trace manager.
 */
public class HibernateTraceInterceptor extends EmptyInterceptor {

    /**
     * Logger
     */
    private static Log log = LogFactory.getLog(HibernateTraceInterceptor.class);

    private static int[] nullIntArray = new int[0];

    /**
     * Called from <tt>flush()</tt>. The return value determines whether the entity is updated
     * <ul>
     * <li>an array of property indices - the entity is dirty
     * <li>and empty array - the entity is not dirty
     * <li><tt>null</tt> - use Hibernate's default dirty-checking algorithm
     * </ul>
     *
     * @param entity a persistent entity
     * @return array of dirty property indices or <tt>null</tt> to choose default behaviour
     */
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        log.debug("findDirty(): " + getIdentifier(entity,id));

        // This is a special case for Contact=-1.  It never contains any data and is shared across Customers
        // so we never want to save it.
        if (entity != null && entity instanceof Contact) {
            final IntegerPk pk = (IntegerPk) ((Contact) entity).getPk();
            if (pk!=null && pk.getEntityID() == -1) {
                return nullIntArray;
            }
        }
        return null;
    }

    /**
     * Instantiate the entity class. Return <tt>null</tt> to indicate that Hibernate should use
     * the default constructor of the class. The identifier property of the returned instance
     * should be initialized with the given identifier.
     *
     * @param clazz a mapped class
     * @param id    the identifier of the new instance
     * @return an instance of the class, or <tt>null</tt> to choose default behaviour
     */
    public Object instantiate(Class clazz, Serializable id) throws CallbackException {
        log.debug("instantiate called for class: " + clazz.getName() + " and id: " + id);

        return null;
    }

    /**
     * Called when a transient entity is passed to <tt>saveOrUpdate()</tt>. The return value determines
     * <ul>
     * <li><tt>Boolean.TRUE</tt> - the entity is passed to <tt>save()</tt>, resulting in an <tt>INSERT</tt>
     * <li><tt>Boolean.FALSE</tt> - the entity is passed to <tt>update()</tt>, resulting in an <tt>UPDATE</tt>
     * <li><tt>null</tt> - Hibernate uses the <tt>unsaved-value</tt> mapping to determine if the object is unsaved
     * </ul>
     *
     * @param entity a transient entity
     * @return Boolean or <tt>null</tt> to choose default behaviour
     */
    public Boolean isUnsaved(Object entity) {
        return null;
    }

    /**
     * Called before an object is deleted. It is not recommended that the interceptor modify the <tt>state</tt>.
     */
    public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException {
        log.info("onDelete():" + getIdentifier(entity,id));
    }

    /**
     * Called just before an object is initialized. The interceptor may change the <tt>state</tt>, which will
     * be propagated to the persistent object. Note that when this method is called, <tt>entity</tt> will be
     * an empty uninitialized instance of the class.
     *
     * @return <tt>true</tt> if the user modified the <tt>state</tt> in any way.
     */
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException {
        log.debug("onLoad(): " + getIdentifier(entity,id));
        return false;
    }

    /**
     * Called after a flush that actually ends in execution of the SQL statements required to synchronize
     * in-memory state with the database.
     */
    public void postFlush(Iterator entities) throws CallbackException {
        log.info("postFlush()");
    }

    /**
     * Called before a flush
     */
    public void preFlush(Iterator entities) throws CallbackException {

        // if debug is not enabled, short-cut out
        if (!log.isDebugEnabled()) {
            return;
        }

        log.debug("preFlush(): start" );
        while (entities.hasNext()) {
            Object entity = entities.next();
            log.debug("  " + entity);
        }

        log.debug("preFlush(): end");
    }

    /**
     * Returns an identifier for a persistent object.  For entities it returns
     * the entity type, for other objects it is the fully qualified class name.
     *
     * @return entityName
     */
    private String getIdentifier(Object persistentObject, Serializable id) {
        return getEntityName(persistentObject) + "[" + id + "]";
    }
    
    /** 
     * privete method that will iterate over the string array of propertyNames searching for a match to the String value
     * passed in propertyToSet. If found, the currentState array is updated to contain the value object passed in.
     */
    private void setValue(Object[] currentState, String[] propertyNames, String propertyToSet, Object value) 
    {
        //NPE prevention
        if(propertyToSet == null || propertyToSet.length() == 0) return;
        
        for(int index = 0; index < propertyNames.length; index++)
        {
            if(propertyToSet.equals(propertyNames[index]))
            {
                currentState[index] = value;
                break;
            }
        }
    }
    
}
